S11-12 Vue-项目:mr-vue3-ts-consult-patient2
[TOC]
Consult
TS类型
知识点:Enum
1、使用枚举类型定义问诊类型、问诊时间
// 问诊类型
export enum ConsultType {
/** 找医生 */
Doctor = 1,
/** 快速问诊 */
Fast = 2,
/** 开药问诊 */
Medication = 3
}
// 问诊时间,以1自增可以省略
export enum IllnessTime {
/** 一周内 */
Week = 1,
/** 一月内 */
Month,
/** 半年内 */
HalfYear,
/** 半年以上 */
More
}
2、定义问诊记录类型
import { ConsultType, IllnessTime } from '@/enums'
// 图片列表
export type Image = {
/** 图片ID */
id: string
/** 图片地址 */
url: string
}
// 问诊记录
export type Consult = {
/** 问诊记录ID */
id: string
/** 问诊类型 */
type: ConsultType
/** 快速问诊类型,0 普通 1 三甲 */
illnessType: 0 | 1
/** 科室ID */
depId: string
/** 疾病描述 */
illnessDesc: string
/** 疾病持续时间 */
illnessTime: IllnessTime
/** 是否就诊过,0 未就诊过 1 就诊过 */
consultFlag: 0 | 1
/** 图片数组 */
pictures: Image[]
/** 患者ID */
patientId: string
/** 优惠券ID */
couponId: string
}
// 问诊记录-全部可选
export type PartialConsult = Partial<Consult>
// Required 转换为全部必须 Partial 转换问全部可选 两个内置的泛型类型
问诊类型
定义问诊仓库
1、在 store/consult.ts 中定义 consultStore
2、在 store/index.ts 中统一导出consultStore
记录问诊类型
1、在 store/consult.ts 中定义 setType() 方法,记录问诊类型
2、在 home 组件调用 setType() 方法,记录问诊类型
问诊级别:consult-fast
路由规则
页面布局
1、HTML
<template>
<div class="consult-fast-page">
<cp-nav-bar title="极速问诊" right-text="问诊记录"></cp-nav-bar>
<div class="fast-logo">
<img class="img" src="@/assets/consult-fast.png" alt="" />
<p class="text"><span>20s</span> 快速匹配专业医生</p>
</div>
<div class="fast-type">
<router-link to="/consult/dep" class="item">
<cp-icon class="pic" name="consult-doctor"></cp-icon>
<div class="info">
<p>三甲图文问诊</p>
<p>三甲主治及以上级别医生</p>
</div>
<van-icon name="arrow"></van-icon>
</router-link>
<router-link to="/consult/dep" class="item">
<cp-icon class="pic" name="consult-message"></cp-icon>
<div class="info">
<p>普通图文问诊</p>
<p>二甲主治及以上级别医生</p>
</div>
<van-icon name="arrow"></van-icon>
</router-link>
</div>
</div>
</template>
2、样式
.consult-fast-page {
padding-top: 46px;
.fast-logo {
padding: 30px 0;
text-align: center;
.img {
width: 240px;
}
.text {
font-size: 16px;
margin-top: 10px;
> span {
color: var(--cp-primary);
}
}
}
.fast-type {
padding: 15px;
.item {
display: flex;
padding: 16px;
border-radius: 4px;
align-items: center;
margin-bottom: 16px;
border: 0.5px solid var(--cp-line);
}
.pic {
width: 40px;
height: 40px;
}
.info {
margin-left: 12px;
flex: 1;
> p:first-child {
font-size: 16px;
color: var(--cp-text1);
margin-bottom: 4px;
}
> p:last-child {
font-size: 13px;
color: var(--cp-tag);
}
}
.van-icon {
color: var(--cp-tip);
}
}
}
记录问诊级别
1、在 store/consult.ts 中定义 setIllnessType() 方法,记录问诊级别
2、在 consult-fast 组件调用 setIllnessType() 方法,记录问诊级别
问诊科室:consult-department
路由规则
页面布局
1、HTML
<template>
<div class="consult-dep-page">
<!-- 导航栏 -->
<cp-nav-bar title="选择科室" />
<div class="wrapper">
<!-- 一级科室 -->
<van-sidebar v-model="active">
<van-sidebar-item title="内科" />
<van-sidebar-item title="外科" />
<van-sidebar-item title="皮肤科" />
<van-sidebar-item title="骨科" />
</van-sidebar>
<!-- 二级科室 -->
<div class="sub-dep">
<router-link to="/consult/illness">科室一</router-link>
<router-link to="/consult/illness">科室二</router-link>
<router-link to="/consult/illness">科室三</router-link>
</div>
</div>
</div>
</template>
2、样式
.van-sidebar {
width: 114px;
&-item {
padding: 14px;
color: var(--cp-tag);
&--select {
color: var(--cp-main);
font-weight: normal;
&::before {
display: none;
}
}
}
}
.consult-dep-page {
padding-top: 46px;
.wrapper {
height: calc(100vh - 46px);
overflow: hidden;
display: flex;
.sub-dep {
flex: 1;
height: 100%;
overflow-y: auto;
> a {
display: block;
padding: 14px 30px;
color: var(--cp-dark);
}
}
}
}
渲染请求数据
TS类型
// 二级科室
export type SubDep = {
/** 科室ID */
id: string
/** 科室名称 */
name: string
}
// 一级科室
export type TopDep = SubDep & {
/** 二级科室数组 */
child: SubDep[]
}
接口
URL:
/dep/all
类型:
GET
token:携带
参数:无
返回数据:
渲染数据
1、在 services/consult.ts 中发送网络请求
2、在组件中调用请求方法,获取一级科室列表数据
3、渲染一级科室数据
4、根据 active 属性,获取二级科室列表数据
5、渲染二级科室数据
记录问诊科室
1、在 store/consult.ts 中定义 setDep() 方法,记录问诊科室
2、在 consult-department 组件调用 setDep() 方法,记录问诊科室
病情描述:consult-illness
路由规则
页面布局
1、HTML
<template>
<div class="consult-illness-page">
<cp-nav-bar title="图文问诊" />
<!-- 医生提示 -->
<div class="illness-tip van-hairline--bottom">
<img class="img" src="@/assets/avatar-doctor.svg" />
<div class="info">
<p class="tit">在线医生</p>
<p class="tip">
请描述你的疾病或症状、是否用药、就诊经历,需要我听过什么样的帮助
</p>
<p class="safe">
<cp-icon name="consult-safe" /><span>内容仅医生可见</span>
</p>
</div>
</div>
<!-- 收集信息 -->
<div class="illness-form">
<van-field
type="textarea"
rows="3"
placeholder="请详细描述您的病情,病情描述不能为空"
></van-field>
<div class="item">
<p>本次患病多久了?</p>
</div>
<div class="item">
<p>此次病情是否去医院就诊过?</p>
</div>
</div>
</div>
</template>
2、样式
.consult-illness-page {
padding-top: 46px;
}
.illness-tip {
display: flex;
padding: 15px;
.img {
width: 52px;
height: 52px;
border-radius: 4px;
overflow: hidden;
margin-top: 10px;
}
.info {
flex: 1;
padding-left: 12px;
.tit {
font-size: 16px;
margin-bottom: 5px;
}
.tip {
padding: 12px;
background: var(--cp-bg);
color: var(--cp-text3);
font-size: 13px;
margin-bottom: 10px;
}
.safe {
font-size: 10px;
color: var(--cp-text3);
display: flex;
align-items: center;
.cp-icon {
font-size: 12px;
margin-right: 2px;
}
}
}
}
.illness-form {
padding: 15px;
.van-field {
padding: 0;
&::after {
border-bottom: none;
}
}
.item {
> p {
color: var(--cp-text3);
padding: 15px 0;
}
}
}
表单数据
1、TS类型
export type ConsultIllness = Pick<
PartialConsult,
'illnessDesc' | 'illnessTime' | 'consultFlag' | 'pictures'
>
2、表单数据
import type { ConsultIllness } from '@/types/consult'
import { ref } from 'vue'
import { IllnessTime } from '@/enums'
const timeOptions = [
{ label: '一周内', value: IllnessTime.Week },
{ label: '一月内', value: IllnessTime.Month },
{ label: '半年内', value: IllnessTime.HalfYear },
{ label: '半年以上', value: IllnessTime.More }
]
const flagOptions = [
{ label: '就诊过', value: 1 },
{ label: '没就诊过', value: 0 }
]
const form = ref<ConsultIllness>({
illnessDesc: '',
illnessTime: undefined,
consultFlag: undefined,
pictures: []
})
渲染数据
上传图片@
页面布局
1、HTML
<div class="illness-img">
<van-uploader></van-uploader>
<p class="tip" >上传内容仅医生可见,最多9张图,最大5MB</p>
</div>
2、样式
.illness-img {
padding-top: 16px;
margin-bottom: 40px;
display: flex;
align-items: center;
.tip {
font-size: 12px;
color: var(--cp-tip);
}
::v-deep() {
.van-uploader {
&__preview {
&-delete {
left: -6px;
top: -6px;
border-radius: 50%;
background-color: var(--cp-primary);
width: 20px;
height: 20px;
&-icon {
transform: scale(0.9) translate(-22%, 22%);
}
}
&-image {
border-radius: 8px;
overflow: hidden;
}
}
&__upload {
border-radius: 8px;
}
&__upload-icon {
color: var(--cp-text3);
}
}
}
}
配置图片文字
限制数量和大小
收集图片数据
实现上传业务
接口
URL:
/upload
类型:
POST
token:携带
参数:
ts{ file: FormData // 上传的文件 }
返回数据:
上传图片
1、在 services/consult.ts 中发送网络请求
2、在组件中,通过after-read
绑定图片上传完毕后的回调函数
3、通过after-read
的item.status参数定义上传状态
4、同步上传数据到form.pictures中
3、在组件中,通过@delete
监听删除文件预览的处理函数
记录病情描述
按钮激活
1、默认情况下按钮有disabled类,此时按钮为灰色状态
2、样式默认为灰色
.van-button {
font-size: 16px;
margin-bottom: 30px;
&.disabled {
opacity: 1;
background: #fafafa;
color: #d9dbde;
border: #fafafa;
}
}
3、激活:当病情描述、时间、是否就诊选中时激活按钮
轻提示校验
记录病情描述
1、在 store/consult.ts 中定义记录病情描述的方法 setIllness()
2、在组件中调用 setIllness() 方法,记录病情描述,并跳转到选择患者页面
回显数据
1、当进入到病情描述页面时,判断是否已经存在病情描述数据,如果存在则回显该数据。
2、通过设置closeOnPopstate: false
处理从选择患者页面回退时,无法弹出弹框的问题
选择患者:patient
界面兼容
依据isChange=1
区分选择患者、家庭档案
选中患者效果
1、在患者项上绑定点击事件,设置选中患者id
2、阻止点击编辑按钮时的事件冒泡
默认选中
思路:进入页面后,如果有默认患者,则选中该患者;如果没有默认患者,则选中第一个患者。
记录患者ID
1、在 store/consult.ts 中,添加记录患者ID的方法 setPatient()
2、在组件中点击下一步时,调用 setPatient() 方法记录患者ID
问诊支付:consult-pay
路由规则
页面布局
1、HTML
<template>
<div class="consult-pay-page">
<cp-nav-bar title="支付" />
<div class="pay-info">
<p class="tit">图文问诊 49 元</p>
<img class="img" src="@/assets/avatar-doctor.svg" />
<p class="desc">
<span>极速问诊</span>
<span>自动分配医生</span>
</p>
</div>
<van-cell-group>
<van-cell title="优惠券" value="-¥10.00" />
<van-cell title="积分抵扣" value="-¥10.00" />
<van-cell title="实付款" value="¥29.00" class="pay-price" />
</van-cell-group>
<div class="pay-space"></div>
<van-cell-group>
<van-cell title="患者信息" value="李富贵 | 男 | 30岁"></van-cell>
<van-cell title="病情描述" label="头痛,头晕,恶心"></van-cell>
</van-cell-group>
<div class="pay-schema">
<van-checkbox>我已同意 <span class="text">支付协议</span></van-checkbox>
</div>
<van-submit-bar button-type="primary" :price="2900" button-text="立即支付" text-align="left" />
</div>
</template>
2、样式
.consult-pay-page {
padding: 46px 0 50px 0;
}
.pay-info {
display: flex;
padding: 15px;
flex-wrap: wrap;
align-items: center;
.tit {
width: 100%;
font-size: 16px;
margin-bottom: 10px;
}
.img {
margin-right: 10px;
width: 38px;
height: 38px;
border-radius: 4px;
overflow: hidden;
}
.desc {
flex: 1;
> span {
display: block;
color: var(--cp-tag);
&:first-child {
font-size: 16px;
color: var(--cp-text2);
}
}
}
}
.pay-price {
::v-deep() {
.vam-cell__title {
font-size: 16px;
}
.van-cell__value {
font-size: 16px;
color: var(--cp-price);
}
}
}
.pay-space {
height: 12px;
background-color: var(--cp-bg);
}
.pay-schema {
height: 56px;
display: flex;
align-items: center;
justify-content: center;
.text {
color: var(--cp-primary);
}
}
::v-deep() {
.van-submit-bar__button {
font-weight: normal;
width: 160px;
}
}
.pay-type {
.amount {
padding: 20px;
text-align: center;
font-size: 16px;
font-weight: bold;
}
.btn {
padding: 15px;
}
.van-cell {
align-items: center;
.cp-icon {
margin-right: 10px;
font-size: 18px;
}
.van-checkbox :deep(.van-checkbox__icon) {
font-size: 16px;
}
}
}
渲染请求数据
TS类型
// 问诊订单预支付传参
export type ConsultOrderPreParams = Pick<PartialConsult, 'type' | 'illnessType'>
// 问诊订单预支付信息
export type ConsultOrderPreData = {
/** 积分抵扣 */
pointDeduction: number
/** 优惠券抵扣 */
couponDeduction: number
/** 优惠券ID */
couponId: string
/** 需付款 */
payment: number
/** 实付款 */
actualPayment: number
}
接口-预支付信息
URL:
/patient/consult/order/pre
类型:
GET
token:携带
参数:
ts{ // 急速问诊只需要以下2个属性 type: string // 问诊类型:1 找医生,2 极速问诊,3 开药问诊 illnessType: string // 极速问诊级别:0 普通,1 三甲 useCoupon: string // 是否使用优惠券:0 不使用优惠券,1 使用优惠券(默认,且按最大优惠券使用)。 couponId: string // 可使用优惠券id docId: string // 医生id,当type为1时必传 }
返回数据:
接口-患者信息
URL:
/patient/info/:id
类型:
GET
token:携带
参数:
ts{ id: string // 患者id }
返回数据:
渲染数据
1、在 services/consult.ts 中发送网络请求
2、在 services/patient.ts 中发送网络请求
3、在组件中,调用请求方法
4、渲染数据
记录优惠券
1、在store中定义 setCoupon() 方法记录优惠券
2、在组件中,获取到预支付信息后,调用 setCoupon() 方法记录优惠券
骨架屏@
实现支付
支付流程
生成订单
支付抽屉面板
1、在 van-submit-bar 上绑定点击事件,打开支付抽屉面板
2、支付抽屉面板布局
接口
URL:
/patient/consult/order
类型:
POST
token:携带
参数:
tsConsult
返回数据:
实现生成订单
1、选择支付方式
2、在 services/consult.ts 中发送网络请求
3、在组件中调用请求方法,获取订单id,同时添加loading效果
清理问诊数据
1、在store中定义 clear() 方法,清理问诊数据
2、在组件中调用 clear() 方法,清理问诊数据
用户引导
阻止返回上页
阻止关闭抽屉
1、设置属性阻止关闭抽屉
2、监听用户点击灰色区域关闭抽屉,弹出确认弹框
刷新页面提示
思路:刷新页面后在onMounted钩子中校验所有需要的key对应的值是否为undefined,如果所有值都不是undefined,则校验通过,否则弹出提示框,提示数据不完整。
实现支付
接口
URL:
/patient/consult/pay
类型:
POST
token:携带
参数:
ts{ paymentMethod: string // 支付方式:0 微信支付,1 支付宝,2 云闪付 orderId: string // 订单id payCallback: string // 回跳地址,http://域名/回跳页面 }
返回数据:
实现支付
1、在 services/consult.ts 中发送网络请求
2、在组件中调用请求方法,获取支付地址
处理支付失败
思路:支付完成后等待几秒会跳转到指定地址,url参数中包括支付结果payResult,支付成功payResult为true,支付失败payResult为false。
在room路由的beforeEnter路由钩子中处理支付失败的跳转
问诊记录:consult-order
路由规则
页面布局
1、HTML
<script setup lang="ts">
import ConsultList from './components/ConsultList.vue'
</script>
<template>
<div class="consult-page">
<cp-nav-bar title="问诊记录" />
<van-tabs sticky>
<van-tab title="极速问诊"><consult-list /></van-tab>
<van-tab title="找医生"><consult-list /></van-tab>
<van-tab title="开药问诊"><consult-list /></van-tab>
</van-tabs>
</div>
</template>
2、样式
.consult-page {
padding-top: 46px;
background-color: var(--cp-bg);
min-height: calc(100vh - 46px);
}
功能
问诊记录入口
1、入口一:在consult-fast组件中,点击问诊记录跳转
2、入口二:在user组件中,点击问诊记录跳转
接口-问诊记录订单列表
URL:
/patient/consult/order/list
类型:
GET
token:携带
参数:
ts{ current: string // 当前页数,默认:1 pageSize: string // 每页记录数,默认:10 type: string // 问诊类型,1:问医生,2:急速问诊,3:开药问诊,默认:1 }
返回数据:
接口-TS类型
1、接口参数类型
export type ConsultOrderListParams = PageParams & {
type: ConsultType // 问诊记录类型
}
2、带分页问诊订单类型
export type ConsultOrderPage = {
pageTotal: number
total: number
rows: ConsultOrderItem[]
}
请求数据
1、在 services/consult.js 中,发送网络请求,获取问诊记录订单列表数据
2、在 consult-record-list 组件中,使用 van-list 组件触发加载更多,并在@load事件处理函数中调用请求方法
3、渲染请求到的问诊记录列表数据
组件:consult-order-list
页面布局
1、HTML
<script setup lang="ts">
import ConsultRecordItem from './ConsultRecordItem.vue'
</script>
<template>
<div class="consult-list">
<consult-record-item v-for="i in 5" :key="i" />
</div>
</template>
2、样式
.consult-list {
padding: 10px 15px;
}
接收问诊类型
1、在consult-record 组件中,传递问诊类型属性type
2、在 consult-record-list 组件中,接收属性
组件:consult-order-item
页面布局
1、HTML
<template>
<div class="consult-record-item">
<div class="head van-hairline--bottom">
<img class="img" src="@/assets/avatar-doctor.svg" />
<p>极速问诊(自动分配医生)</p>
<span>待支付</span>
</div>
<div class="body">
<div class="body-row">
<div class="body-label">病情描述</div>
<div class="body-value">腹痛腹泻 胃部有些痉挛</div>
</div>
<div class="body-row">
<div class="body-label">价格</div>
<div class="body-value">¥ 39.00</div>
</div>
<div class="body-row">
<div class="body-label">创建时间</div>
<div class="body-value tip">2019-07-08 09:55:54</div>
</div>
</div>
<div class="foot">
<van-button class="gray" plain size="small" round>取消问诊</van-button>
<van-button type="primary" plain size="small" round >去支付</van-button>
</div>
</div>
</template>
2、样式
.consult-record-item {
border-radius: 4px;
box-shadow: 0px 0px 22px 0px rgba(245, 245, 245, 0.1);
background-color: #fff;
margin-bottom: 10px;
.head {
display: flex;
align-items: center;
height: 50px;
padding: 0 15px;
.img {
width: 20px;
height: 20px;
}
> p {
flex: 1;
font-size: 15px;
padding-left: 10px;
}
> span {
color: var(--cp-tag);
&.orange {
color: #f2994a;
}
&.green {
color: var(--cp-primary);
}
}
}
.body {
padding: 15px 15px 8px 15px;
.body-row {
display: flex;
margin-bottom: 7px;
}
.body-label {
width: 62px;
font-size: 13px;
color: var(--cp-tip);
}
.body-value {
width: 250px;
&.tip {
color: var(--cp-tip);
}
}
}
.foot {
padding: 0 15px 15px 15px;
display: flex;
justify-content: flex-end;
align-items: center;
.van-button {
margin-left: 10px;
padding: 0 16px;
&.gray {
color: var(--cp-text3);
background-color: var(--cp-bg);
}
}
.more {
color: var(--cp-tag);
flex: 1;
font-size: 13px;
}
}
}
渲染组件
1、渲染卡片头部和主体
2、渲染卡片底部按钮
功能-更多操作
1、使用 van-popover 组件实现更多气泡
2、添加选择事件 @select
3、设置弹出位置为 top-start
功能-取消问诊
接口-取消问诊订单
URL:
/patient/order/cancel/${id}
类型:
PUT
token:携带
参数:
ts{ id: string // 订单id }
返回数据:
实现取消问诊
1、在 services/consult.js 中发送网络请求
2、在 consult-record-item 组件中,实现取消订单
3、为所有的 取消问诊 按钮绑定点击事件
功能-删除订单
接口-删除订单
URL:
/patient/order/${id}
类型:
DELETE
token:携带
参数:
ts{ id: string // 订单id }
返回数据:
实现删除订单
1、在 services/consult.js 中发送网络请求
2、在 consult-record-item 组件中,实现删除订单,并发射 on-delete 事件到父组件
3、为所有的 删除订单 按钮绑定点击事件
4、更多操作 中也有删除订单按钮,需要在onSelect()方法中调用deleteConsultOrder()方法实现删除订单
5、在父组件 consult-order-list 中,接收发射过来的事件 on-delete,并绑定到onDelete()中实现删除对应的item订单数据
功能-查看处方
1、封装 room-message 组件中的查看处方方法到hooks中
2、使用封装的方法
3、在更多操作的@select中使用封装的方法
Room
路由规则
页面布局
问诊室
<script setup lang="ts">
import RoomStatus from './components/RoomStatus.vue'
import RoomAction from './components/RoomAction.vue'
import RoomMessage from './components/RoomMessage.vue'
</script>
<template>
<div class="room-page">
<cp-nav-bar title="问诊室" />
<room-status></room-status>
<room-message></room-message>
<room-action></room-action>
</div>
</template>
状态栏
<template>
<div class="room-status">
<!-- 待接诊 -->
<div class="wait">已通知医生尽快接诊,24小时内医生未回复将自动退款</div>
<!-- 咨询中 -->
<div class="chat">
<span>咨询中</span>
<span>剩余时间:23:10:34</span>
</div>
<!-- 已结束 -->
<div class="end"><van-icon name="passed" />已结束</div>
</div>
</template>
操作栏
注意:操作栏默认处于禁用状态
<template>
<div class="room-action">
<van-field
type="text"
class="input"
:border="false"
placeholder="问医生"
autocomplete="off"
:disabled="true"
></van-field>
<van-uploader :preview-image="false" :disabled="true">
<cp-icon name="consult-img" />
</van-uploader>
</div>
</template>
消息卡片
<template>
<!-- 患者卡片 -->
<div class="msg msg-illness">
<div class="patient van-hairline--bottom">
<p>李富贵 男 31岁</p>
<p>一周内 | 未去医院就诊</p>
</div>
<van-row>
<van-col span="6">病情描述</van-col>
<van-col span="18">头痛、头晕、恶心</van-col>
<van-col span="6">图片</van-col>
<van-col span="18">点击查看</van-col>
</van-row>
</div>
<!-- 通知-通用 -->
<div class="msg msg-tip">
<div class="content">
<span>医护人员正在赶来,请耐心等候</span>
</div>
</div>
<!-- 通知-温馨提示 -->
<div class="msg msg-tip">
<div class="content">
<span class="green">温馨提示:</span>
<span>在线咨询不能代替面诊,医护人员建议仅供参考</span>
</div>
</div>
<!-- 通知-结束 -->
<div class="msg msg-tip msg-tip-cancel">
<div class="content">
<span>订单取消</span>
</div>
</div>
<!-- 发送文字 -->
<div class="msg msg-to">
<div class="content">
<div class="time">20:12</div>
<div class="pao">大夫你好?</div>
</div>
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
</div>
<!-- 发送图片 -->
<div class="msg msg-to">
<div class="content">
<div class="time">20:12</div>
<van-image
fit="contain"
src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg"
/>
</div>
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
</div>
<!-- 接收文字 -->
<div class="msg msg-from">
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
<div class="content">
<div class="time">20:12</div>
<div class="pao">哪里不舒服</div>
</div>
</div>
<!-- 接收图片 -->
<div class="msg msg-from">
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
<div class="content">
<div class="time">20:12</div>
<van-image
fit="contain"
src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg"
/>
</div>
</div>
<!-- 处方卡片 -->
<div class="msg msg-recipe">
<div class="content">
<div class="head van-hairline--bottom">
<div class="head-tit">
<h3>电子处方</h3>
<p>原始处方 <van-icon name="arrow"></van-icon></p>
</div>
<p>李富贵 男 31岁 血管性头痛</p>
<p>开方时间:2022-01-15 14:21:42</p>
</div>
<div class="body">
<div class="body-item" v-for="i in 2" :key="i">
<div class="durg">
<p>优赛明 维生素E乳</p>
<p>口服,每次1袋,每天3次,用药3天</p>
</div>
<div class="num">x1</div>
</div>
</div>
<div class="foot"><span>购买药品</span></div>
</div>
</div>
<!-- 评价卡片,后期实现 -->
</template>
<style lang="scss" scoped>
@import '@/styles/room.scss';
</style>
TS类型
1、枚举类型
// 消息类型
export enum MsgType {
/** 文字聊天 */
MsgText = 1,
/** 消息聊天 */
MsgImage = 4,
/** 患者信息 */
CardPat = 21,
/** 处方信息 */
CardPre = 22,
/** 未评价信息 */
CardEvaForm = 23,
/** 已评价信息 */
CardEva = 24,
/** 通用通知 */
Notify = 31,
/** 温馨提示 */
NotifyTip = 32,
/** 取消提示 */
NotifyCancel = 33
}
// 处方状态
export enum PrescriptionStatus {
/** 未付款 */
NotPayment = 1,
/** 已付款 */
Payment = 2,
/** 已失效 */
Invalid = 3
}
2、问诊室类型
import { MsgType, PrescriptionStatus } from '@/enums'
import type { Consult, Image } from './consult'
import type { Patient } from './user'
export type Medical = {
/** 药品ID */
id: string
/** 药品名称 */
name: string
/** 金额 */
amount: string
/** 药品图片 */
avatar: string
/** 规格信息 */
specs: string
/** 用法用量 */
usageDosag: string
/** 数量 */
quantity: string
/** 是否处方,0 不是 1 是 */
prescriptionFlag: 0 | 1
}
export type Prescription = {
/** 处方ID */
id: string
/** 药品订单ID */
orderId: string
/** 创建时间 */
createTime: string
/** 患者名称 */
name: string
/** 问诊记录ID */
recordId: string
/** 性别 0 女 1 男 */
gender: 0 | 1
/** 性别文字 */
genderValue: ''
/** 年龄 */
age: number
/** 诊断信息 */
diagnosis: string
/** 处方状态 */
status: PrescriptionStatus
/** 药品清单 */
medicines: Medical[]
}
export type EvaluateDoc = {
/** 评价ID */
id?: string
/** 评分 */
score?: number
/** 内容 */
content?: string
/** 创建时间 */
createTime?: string
/** 创建人 */
creator?: string
}
export type Message = {
/** 消息ID */
id: string
/** 消息类型 */
msgType: MsgType
/** 发信人 */
from?: string
/** 发信人ID */
fromAvatar?: string
/** 收信人 */
to?: string
/** 收信人头像 */
toAvatar?: string
/** 创建时间 */
createTime: string
/** 消息主体 */
msg: {
/** 文本内容 */
content?: string
/** 图片对象 */
picture?: Image
/** 问诊记录,患者信息 */
consultRecord?: Consult & {
patientInfo: Patient
}
/** 处方信息 */
prescription?: Prescription
/** 评价信息 */
evaluateDoc?: EvaluateDoc
}
}
// 消息分组列表
export type TimeMessages = {
/** 分组消息最早时间 */
createTime: string
/** 消息数组 */
items: Message[]
/** 订单ID */
orderId: string
/** 会话ID */
sid: string
}
WebSocket
接口
建立连接
1、安装socket.io-client
pnpm i socket.io-client
2、在 onMounted/onUnmounted 钩子中,建立/关闭socket连接
聊天记录
获取数据
在 onMounted 钩子中,监听 chatMsgList
,获取聊天记录。
渲染聊天记录
1、在room组件中,传入获取到的聊天记录数据,并遍历传递到room-message组件中
2、在room-message组件中,渲染聊天记录消息
2.1、抽取常量数据
2.2、根据常量数据转换聊天记录数据类型
2.3、渲染页面
3、预览图片
渲染通用通知
渲染温馨提示
接诊状态
TS类型
1、问诊订单状态枚举类型
export enum OrderType {
// 问诊订单
/** 待支付 */
ConsultPay = 1,
/** 待接诊 */
ConsultWait = 2,
/** 问诊中 */
ConsultChat = 3,
/** 问诊完成 */
ConsultComplete = 4,
/** 取消问诊 */
ConsultCancel = 5,
// 药品订单
/** 待支付 */
MedicinePay = 10,
/** 待发货 */
MedicineSend = 11,
/** 待收货 */
MedicineTake = 12,
/** 已完成 */
MedicineComplete = 13,
/** 取消订单 */
MedicineCancel = 14
}
2、问诊订单TS类型
// 问诊订单单项信息
export type ConsultOrderItem = Consult & {
/** 创建时间 */
createTime: string
/** 医生信息 */
docInfo?: Doctor
/** 患者信息 */
patientInfo: Patient
/** 订单编号 */
orderNo: string
/** 订单状态 */
status: OrderType
/** 状态文字 */
statusValue: string
/** 类型问诊文字 */
typeValue: string
/** 倒计时时间 */
countdown: number
/** 处方ID */
prescriptionId?: string
/** 评价ID */
evaluateId: number
/** 应付款 */
payment: number
/** 优惠券抵扣 */
couponDeduction: number
/** 积分抵扣 */
pointDeduction: number
/** 实付款 */
actualPayment: number
}
接口
URL:
/patient/consult/order/detail
类型:
GET
token:携带
参数:
ts{ orderId: string // 订单id }
返回数据:ConsultOrderItem
请求数据
1、在 services/room 中发送网络请求
2、在 room 组件的 onMounted 钩子中,调用请求方法,获取数据
3、在 room 组件的订单状态变化时,重新获取数据
控制组件
控制状态栏
1、传递订单详情数据给 room-status 组件
2、在 room-status 组件中接收传递的数据
3、根据传递的数据渲染不同的状态
控制操作栏
1、根据获取的订单详情数据,传递disabled值给 room-action 组件
2、在 room-action 组件中接收 disabled 属性
3、根据 disabled 属性,控制操作栏是否可用
文字聊天
发送文字
1、在 room-action 组件中,监听 enter 键点击事件,发送 send-text 事件到父组件
2、在 room 组件中,监听 send-text 事件,实现socket发送文字消息
渲染文字
1、在 room 组件中通过socket接收聊天消息
2、在 room-message 组件中,渲染接收的聊天消息
3、接收消息后,在nextTick()后,滚动页面到底部
4、修改时间格式
4.1、安装 dayjs 插件:pnpm i dayjs
4.2、定义格式化时间方法
4.3、调用方法
图片聊天
1、上传图片:在 room-action 组件中,绑定 sendImage() 方法到 after-read 属性上
2、在 sendImage() 方法中,调用 uploadImage() 方法发送网络请求,上传图片,并向外发射 send-image 事件
3、发送图片socket:在 room 组件中,绑定onSendImage() 方法到 send-image 事件
4、在 onSendImage() 方法中,通过socket发送图片消息
5、渲染消息:
聊天记录
默认显示最新消息
下拉刷新获取聊天记录
1、使用 van-pull-refresh 组件包裹 room-message,实现下拉刷新
2、在下拉刷新处理函数 onRefresh() 中,通过socket获取历史聊天记录
3、记录每一段消息中最早的消息时间,并在加载完毕后设置loading为false,并在没有聊天记录数据时提示
消息已读
接口-所有未读消息数量
URL:
/patient/message/unRead/all
类型:
GET
token:携带
参数:无
返回数据:
渲染数据
1、在 services/room.js 中发送网络请求
2、在 layout 组件的 onMounted 中调用请求方法,获取数据
3、渲染数据到 layout 组件的消息通知项中
实现消息已读
1、在room 组件的 onMounted 中,在第一次进入聊天室时,设置消息已读
2、在聊天时,每收到一条消息就将其设置为已读
查看处方
渲染处方卡片
接口-获取处方图片
URL:
/patient/consult/prescription/:id
类型:
GET
token:携带
参数:
id: string // 处方id
返回数据:
渲染数据
1、在 services/room.js 中发送网络请求,获取处方图片地址
2、在 room-message 组件中,绑定点击事件 onShowPrescription()
3、在onShowPrescription()中,实现预览处方图片
评价医生:evaluate-card
展示结束问诊消息
展示评价卡片
1、HTML
<template>
<!-- 已评价 -->
<div class="evaluate-card">
<p class="title">医生服务评价</p>
<p class="desc">我们会更加努力提升服务质量</p>
<van-rate
:modelValue="3"
size="7vw"
gutter="3vw"
color="#FADB14"
void-icon="star"
void-color="rgba(0,0,0,0.04)"
/>
</div>
<!-- 未评价 -->
<div class="evaluate-card">
<p class="title">感谢您的评价</p>
<p class="desc">本次在线问诊服务您还满意吗?</p>
<van-rate
size="7vw"
gutter="3vw"
color="#FADB14"
void-icon="star"
void-color="rgba(0,0,0,0.04)"
/>
<van-field
type="textarea"
maxlength="150"
show-word-limit
rows="3"
placeholder="请描述您对医生的评价或是在医生看诊过程中遇到的问题"
></van-field>
<div class="footer">
<van-checkbox>匿名评价</van-checkbox>
<van-button type="primary" size="small" round> 提交评价 </van-button>
</div>
</div>
</template>
2、样式
.evaluate-card {
width: 100%;
background-color: #fff;
border-radius: 8px;
overflow: hidden;
text-align: center;
padding: 15px;
.title {
font-size: 15px;
margin-bottom: 5px;
}
.desc {
font-size: 12px;
margin-bottom: 15px;
color: var(--cp-tip);
}
.van-field {
background-color: var(--cp-bg);
margin: 15px 0;
border-radius: 8px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
:deep() {
.van-checkbox {
.van-icon {
font-size: 12px;
}
&__label {
font-size: 12px;
color: var(--cp-tip);
}
height: 16px;
}
.van-button {
padding: 0 16px;
&.disabled {
opacity: 1;
background: #fafafa;
color: #d9dbde;
border: #fafafa;
}
}
}
}
}
渲染数据
1、使用评价卡片组件
2、在评价卡片组件中,接收传递的数据
3、根据 evaluateDoc 是否有数据,展示不同的内容
收集评价信息
1、定义评价数据
2、绑定数据到表单中
3、绑定提交按钮点击方法 onSubmit,并在其中提交评价信息
4、提交评价前校验
接口-评价医生
URL:
/patient/order/evaluate
类型:
POST
token:携带
参数:
ts{ docId: string // 评价的医生id orderId: string // 订单id score: number // 分数 content: string // 评价内容 anonymousFlag: 0 | 1 // 匿名标志,0:不是匿名,1:是匿名 }
返回数据:
提交评价
1、在 services/room.js 中,发送网络请求
2、docId
和orderId
需要由祖父组件room的consult传递
3、在后代组件 evaluate-card 中,使用 inject() 注入provide() 提供的响应式数据
4、在 evaluate-card 组件的onSubmit中,调用请求方法实现评价医生
修改父组件中的数据
1、在祖父组件 room 中,定义completeEva()方法找打并修改评价消息数据,并将其提供给子孙组件
2、在子孙组件 evaluate-card 中,注入祖父组件提供的completeEva()方法,并调用它
3、渲染已评价卡片